#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "config_parse.h"

static char *version = "v0.0.4";

void config_parse_init(struct config_list *list) {
    // if (CONFIG_KEY_LEN < sizeof(__SPACE_LINE_SIGN__)) {
    //     fprintf(stderr, "CONFIG_KEY_LEN need to larger than __SPACE_LINE_SIGN__\n");
    // }
    list->key[0] = list->value[0] = 0;
    list->next = NULL;
}

static void config_parse_freec(struct config_list *list) {
    if(list->next) {
        config_parse_freec(list->next);
    }
    // printf("free key: %s\n",list->key);
    free(list);
}

void config_parse_free(struct config_list *list) {
    if (list && list->next) config_parse_freec(list->next);
    list->next = NULL;
}

static void clean_tail(char *s, int v) {
    for(; v>0; v--) {
        if (s[v]==0 || 
            s[v]=='\r' || 
            s[v]=='\n' || 
            s[v]=='\t')
        {
            s[v] = 0;
        } else {
            break;
        }
    }
}

static char *findCharLast(char *src, char ch) 
{
    char *dest = NULL;
    while (*src)
    {
        if (*src == ch) dest = src; 
        src++;
    }
    return dest;
}

#define CPL_LINE (1<<0)
#define TOPIC_HEAD (1<<1)
#define TOPIC_DATA (1<<2)

int config_parse_file(struct config_list *list, char *file_name) {
    if (!list || !file_name) return -1;
    int line = 0, ctl_flag = CPL_LINE, tl;
    // char *topic;
    char reb[CONFIG_KEY_LEN + CONFIG_VALUE_LEN + 64];
    FILE *fp = fopen(file_name, "r");
    if (!fp) {
        perror("fopen");
        return -1;
    }

    struct config_list *mark = list;
    while (fgets(reb, sizeof(reb), fp)) {
        // =================================copy==================================
        if (!(ctl_flag&CPL_LINE)) {
            // printf("throuth line: %s", reb);
            tl = strlen(reb);
            if (tl && reb[tl-1]=='\n')  ctl_flag |= CPL_LINE;
            continue;
        } else if (!strncmp(reb, "[", 1)) {
            if (ctl_flag & (TOPIC_HEAD|TOPIC_DATA)) {
                strncpy(mark->key, __SPACE_LINE_SIGN__, CONFIG_KEY_LEN);
                mark->next = (struct config_list*)malloc(sizeof(struct config_list));
                if (!mark->next) break;
                mark = mark->next;
                config_parse_init(mark);
                line++;
            }
            ctl_flag |= (TOPIC_HEAD|TOPIC_DATA);
        } else if (!strncmp(reb, "#", 1) || !strncmp(reb, "\n", 1) || !strncmp(reb, ";", 1)) {   // 忽略注释(#)和空行
            if ((ctl_flag&TOPIC_DATA) && !strncmp(reb, "\n", 1)) {
                strncpy(mark->key, __SPACE_LINE_SIGN__, CONFIG_KEY_LEN);
                mark->next = (struct config_list*)malloc(sizeof(struct config_list));
                if (!mark->next) break;
                mark = mark->next;
                config_parse_init(mark);
                line++;
                ctl_flag &= ~TOPIC_DATA;
            }
            continue;
        }
        line++;
        // printf("read line %d: %s", line, reb);

        int tl = strlen(reb);
        if (tl && reb[--tl]!='\n') ctl_flag &= ~CPL_LINE;
        // key
        int i=0;
        if (ctl_flag&TOPIC_HEAD) {
            strncpy(mark->key, __TOPIC_SIGN__, CONFIG_KEY_LEN);
            reb[i] = ' ';
        } else {
            while(reb[i] != ' ' && reb[i] != '=' && i<CONFIG_KEY_LEN-1 && i<tl) {
                mark->key[i] = reb[i];
                i++;
            }
            mark->key[i] = 0;
            clean_tail(mark->key, i);
            // printf("key = %s\n", mark->key);
        }

        // move
        while (reb[i]!=' ' && reb[i]!='=' && i<tl) i++;
        if (i<tl) {
            while (reb[i] == ' ') i++;
            if (reb[i] == '=') i++;
            while (reb[i] == ' ') i++;
        }
        
        // value 
        int v=0;
        if(ctl_flag&TOPIC_HEAD) {
            while (reb[i] != ']' && v<CONFIG_VALUE_LEN-1 && i<tl) {
                mark->value[v++] = reb[i++];
            }
            mark->value[v] = 0;
            if (reb[i] != ']') {
                ctl_flag &= ~(TOPIC_HEAD|TOPIC_DATA);
                line--;
                continue;
            } else {
                ctl_flag &= ~(TOPIC_HEAD);
            }
            
        } else if (reb[i]=='#' || reb[i]=='\n' || reb[i]==';') {
            // leave
        } else {
            if (reb[i] == '"') {
                while (reb[++i] != '"' && v<CONFIG_VALUE_LEN-1 && i<tl) {
                    mark->value[v++] = reb[i];
                }
            } else if (reb[i] == '\'') {
                while (reb[++i] != '\'' && v<CONFIG_VALUE_LEN-1 && i<tl) {
                    mark->value[v++] = reb[i];
                }
            } else {
                while (reb[i]!=' ' && reb[i]!=0 && v<CONFIG_VALUE_LEN-1 && i<tl) {
                    mark->value[v++] = reb[i++];
                }
            }
            mark->value[v] = 0;
            clean_tail(mark->value, v);
        }
        // printf("value = %s\n\n", mark->value);

        // next
        mark->next = (struct config_list*)malloc(sizeof(struct config_list));
        if (!mark->next) break;
        mark = mark->next;
        config_parse_init(mark);
        // =================================end===================================
    }

    fclose(fp);
    return line;
}

static int buffer_read_line(char *buff, int buff_len, char* buffer) {
    if (!buff || !buff_len || !buffer) return 0;

    int i=0;
    while (buffer[i]!='\0' && i<buff_len-1)
    {
        buff[i] = buffer[i];
        i++;
        if (buffer[i-1]=='\n') break;
    }
    buff[i] = 0;
    return i;
}

int config_parse_buffer(struct config_list *list, char *buffer) {
    if (!list || !buffer) return -1;

    int line = 0, ctl_flag = CPL_LINE, tl, ret;
    char reb[CONFIG_KEY_LEN + CONFIG_VALUE_LEN + 64];

    struct config_list *mark = list;
    while (ret = buffer_read_line(reb, sizeof(reb), buffer)) {
        buffer += ret;
        // =================================copy==================================
        if (!(ctl_flag&CPL_LINE)) {
            // printf("throuth line: %s", reb);
            tl = strlen(reb);
            if (tl && reb[tl-1]=='\n')  ctl_flag |= CPL_LINE;
            continue;
        } else if (!strncmp(reb, "[", 1)) {
            if (ctl_flag & (TOPIC_HEAD|TOPIC_DATA)) {
                strncpy(mark->key, __SPACE_LINE_SIGN__, CONFIG_KEY_LEN);
                mark->next = (struct config_list*)malloc(sizeof(struct config_list));
                if (!mark->next) break;
                mark = mark->next;
                config_parse_init(mark);
                line++;
            }
            ctl_flag |= (TOPIC_HEAD|TOPIC_DATA);
        } else if (!strncmp(reb, "#", 1) || !strncmp(reb, "\n", 1) || !strncmp(reb, ";", 1)) {   // 忽略注释(#)和空行
            if ((ctl_flag&TOPIC_DATA) && !strncmp(reb, "\n", 1)) {
                strncpy(mark->key, __SPACE_LINE_SIGN__, CONFIG_KEY_LEN);
                mark->next = (struct config_list*)malloc(sizeof(struct config_list));
                if (!mark->next) break;
                mark = mark->next;
                config_parse_init(mark);
                line++;
                ctl_flag &= ~TOPIC_DATA;
            }
            continue;
        }
        line++;
        // printf("read line %d: %s", line, reb);

        int tl = strlen(reb);
        if (tl && reb[--tl]!='\n') ctl_flag &= ~CPL_LINE;
        // key
        int i=0;
        if (ctl_flag&TOPIC_HEAD) {
            strncpy(mark->key, __TOPIC_SIGN__, CONFIG_KEY_LEN);
            reb[i] = ' ';
        } else {
            while(reb[i] != ' ' && reb[i] != '=' && i<CONFIG_KEY_LEN-1 && i<tl) {
                mark->key[i] = reb[i];
                i++;
            }
            mark->key[i] = 0;
            clean_tail(mark->key, i);
            // printf("key = %s\n", mark->key);
        }

        // move
        while (reb[i]!=' ' && reb[i]!='=' && i<tl) i++;
        if (i<tl) {
            while (reb[i] == ' ') i++;
            if (reb[i] == '=') i++;
            while (reb[i] == ' ') i++;
        }
        
        // value 
        int v=0;
        if(ctl_flag&TOPIC_HEAD) {
            while (reb[i] != ']' && v<CONFIG_VALUE_LEN-1 && i<tl) {
                mark->value[v++] = reb[i++];
            }
            mark->value[v] = 0;
            if (reb[i] != ']') {
                ctl_flag &= ~(TOPIC_HEAD|TOPIC_DATA);
                line--;
                continue;
            } else {
                ctl_flag &= ~(TOPIC_HEAD);
            }
            
        } else if (reb[i]=='#' || reb[i]=='\n' || reb[i]==';') {
            // leave
        } else {
            if (reb[i] == '"') {
                while (reb[++i] != '"' && v<CONFIG_VALUE_LEN-1 && i<tl) {
                    mark->value[v++] = reb[i];
                }
            } else if (reb[i] == '\'') {
                while (reb[++i] != '\'' && v<CONFIG_VALUE_LEN-1 && i<tl) {
                    mark->value[v++] = reb[i];
                }
            } else {
                while (reb[i]!=' ' && reb[i]!=0 && v<CONFIG_VALUE_LEN-1 && i<tl) {
                    mark->value[v++] = reb[i++];
                }
            }
            mark->value[v] = 0;
            clean_tail(mark->value, v);
        }
        // printf("value = %s\n\n", mark->value);

        // next
        mark->next = (struct config_list*)malloc(sizeof(struct config_list));
        if (!mark->next) break;
        mark = mark->next;
        config_parse_init(mark);
        // =================================end===================================
    }
    return line;
}

char* config_parse_get_value_by_key(struct config_list *list, char *key, char *topic) {
    if (!key) return NULL;
    char *topic_cur = NULL;
    int mach_topic = 0;
    struct config_list *cur = list;
    if (!topic) mach_topic = 1;
    while (cur) {
        if (strcmp(cur->key, __TOPIC_SIGN__)==0) {
            topic_cur = cur->value;
            if (topic && strcmp(topic, topic_cur)==0) mach_topic=1;
            else mach_topic=0;
            cur = cur->next;
            continue;
        }
        if (topic_cur && strcmp(cur->key, __SPACE_LINE_SIGN__)==0) {
            topic_cur = NULL;
            if (!topic) mach_topic = 1;
            else mach_topic = 0;
            cur = cur->next;
            continue;
        }
        
        if (mach_topic && strcmp(cur->key, key)==0) return cur->value;
        cur = cur->next;
    }
    return NULL;
}

int config_parse_add_empty(struct config_list *list) {
//    if (!list) return -1;
//    while (list->next) list = list->next;
//    list->next = (struct config_list*)malloc(sizeof(struct config_list));
//    if (!list->next) return -1;
    return config_parse_add_line(list, __SPACE_LINE_SIGN__, "");
}


int config_parse_add_note(struct config_list *list, char *note) {
    return config_parse_add_line(list, __NOTE_SIGN__, note);
}

int config_parse_add_topic(struct config_list *list, char *topic) {
    return config_parse_add_line(list, __TOPIC_SIGN__, topic);
}

int config_parse_add_line(struct config_list *list, char *key, char *value) {
    if (!list || !value || !key) return -1;
    while (list->next) list = list->next;
    list->next = (struct config_list*)malloc(sizeof(struct config_list));
    if (!list->next) return -1;
    list = list->next;
    strncpy(list->key, key, sizeof(list->key));
    list->key[sizeof(list->key)-1] = 0;
    strncpy(list->value, value, sizeof(list->value));
    list->value[sizeof(list->value)-1] = 0;
    list->next = NULL;
    return 0;
}

int config_parse_create_file(struct config_list *list, char *file_name) {
    if (!list || !file_name) return -1;
    FILE *fp = fopen(file_name, "w");
    if (!fp) {
        perror("fopen");
        return -1;
    }

    char buff[CONFIG_KEY_LEN+CONFIG_VALUE_LEN+10];
    do {
        if (!list->key[0] && !list->value[0]) {
            buff[0] = '\n';
            buff[1] = 0;
        } else if (strcmp(list->key, __NOTE_SIGN__)==0) {
            snprintf(buff, sizeof(buff), "# %s\n", list->value);
        } else if (strcmp(list->key, __TOPIC_SIGN__)==0) {
            snprintf(buff, sizeof(buff), "[%s]\n", list->value);
        } else if (strcmp(list->key, __SPACE_LINE_SIGN__)==0) {
            snprintf(buff, sizeof(buff), "\n");
        } else {
            snprintf(buff, sizeof(buff), "%s = %s\n", list->key, list->value);
        }
        if (fwrite(buff, 1, strlen(buff), fp) == 0) {
            perror("fwrite");
            fclose(fp);
            return -2;
        }
    }while (list = list->next);
    fclose(fp);
    return 0;
}

int config_parse_create_buffer(struct config_list *list, char *buffer, int buffer_len) {
    if (!list || !buffer) return -1;
    int len, t=0;
    char buff[CONFIG_KEY_LEN+CONFIG_VALUE_LEN+10];
    do {
        if (!list->key[0] && !list->value[0]) {
            buff[0] = '\n';
            buff[1] = 0;
        } else if (strcmp(list->key, __NOTE_SIGN__)==0) {
            snprintf(buff, sizeof(buff), "# %s\n", list->value);
        } else if (strcmp(list->key, __TOPIC_SIGN__)==0) {
            snprintf(buff, sizeof(buff), "[%s]\n", list->value);
        } else if (strcmp(list->key, __SPACE_LINE_SIGN__)==0) {
            snprintf(buff, sizeof(buff), "\n");
        } else {
            snprintf(buff, sizeof(buff), "%s = %s\n", list->key, list->value);
        }
        len = strlen(buff);
        if (t+len > buffer_len) return -2;
        strcpy(buffer+t, buff);
        t+=len;
    }while (list = list->next);

    return 0;
}

int config_parse_add_list(struct config_list *dest, struct config_list *list) {
    if (!dest || !list) return -1;
    while (dest->next) dest = dest->next;
    struct config_list *tmp = (struct config_list*)malloc(sizeof(struct config_list));
    if (!tmp) return -1;
    memcpy(tmp, list, sizeof(struct config_list));
    dest->next = tmp;
    return 0;
}

struct config_list *config_parse_get_line(struct config_list *list, int line) {
    if (line <= 0 || !list) return NULL;
    struct config_list *cur = list;
    while (--line) {
        if (!cur->next) return NULL;
        cur = cur->next;
    }
    return cur;
}

struct config_list *config_parse_get_topic(struct config_list *list, int num) {
    if (num <= 0 || !list) return NULL;
    struct config_list *cur = list;
    while (cur) {
        if (!strcmp(cur->key, __TOPIC_SIGN__)) {
            if (!--num) break;
        }
        cur = cur->next;
    }
    return cur;
}

struct config_list *config_parse_get_topic_line(struct config_list *list, char *topic, int line) {
    if (line <= 0 || !list) return NULL;
    struct config_list *cur = list;
    while (cur) {
        if (!strcmp(cur->key, __TOPIC_SIGN__) && !strcmp(cur->value, topic)) break;
        cur = cur->next;
    }
    if (!cur) return cur;
    cur = cur->next;
    while (cur) {
        if (!strcmp(cur->key, __TOPIC_SIGN__) || !strcmp(cur->key, __SPACE_LINE_SIGN__)) return NULL;
        if (!--line) break; 
        cur = cur->next;
    }
    return cur;
}
